package org.vaulttec.velocity.ui.editor.parser;

import java.util.Stack;

import org.apache.velocity.runtime.parser.node.ASTAddNode;
import org.apache.velocity.runtime.parser.node.ASTAndNode;
import org.apache.velocity.runtime.parser.node.ASTAssignment;
import org.apache.velocity.runtime.parser.node.ASTBlock;
import org.apache.velocity.runtime.parser.node.ASTComment;
import org.apache.velocity.runtime.parser.node.ASTDirective;
import org.apache.velocity.runtime.parser.node.ASTDivNode;
import org.apache.velocity.runtime.parser.node.ASTEQNode;
import org.apache.velocity.runtime.parser.node.ASTElseIfStatement;
import org.apache.velocity.runtime.parser.node.ASTElseStatement;
import org.apache.velocity.runtime.parser.node.ASTEscape;
import org.apache.velocity.runtime.parser.node.ASTEscapedDirective;
import org.apache.velocity.runtime.parser.node.ASTExpression;
import org.apache.velocity.runtime.parser.node.ASTFalse;
import org.apache.velocity.runtime.parser.node.ASTFloatingPointLiteral;
import org.apache.velocity.runtime.parser.node.ASTGENode;
import org.apache.velocity.runtime.parser.node.ASTGTNode;
import org.apache.velocity.runtime.parser.node.ASTIdentifier;
import org.apache.velocity.runtime.parser.node.ASTIfStatement;
import org.apache.velocity.runtime.parser.node.ASTIntegerLiteral;
import org.apache.velocity.runtime.parser.node.ASTIntegerRange;
import org.apache.velocity.runtime.parser.node.ASTLENode;
import org.apache.velocity.runtime.parser.node.ASTLTNode;
import org.apache.velocity.runtime.parser.node.ASTMap;
import org.apache.velocity.runtime.parser.node.ASTMethod;
import org.apache.velocity.runtime.parser.node.ASTModNode;
import org.apache.velocity.runtime.parser.node.ASTMulNode;
import org.apache.velocity.runtime.parser.node.ASTNENode;
import org.apache.velocity.runtime.parser.node.ASTNotNode;
import org.apache.velocity.runtime.parser.node.ASTObjectArray;
import org.apache.velocity.runtime.parser.node.ASTOrNode;
import org.apache.velocity.runtime.parser.node.ASTReference;
import org.apache.velocity.runtime.parser.node.ASTSetDirective;
import org.apache.velocity.runtime.parser.node.ASTStop;
import org.apache.velocity.runtime.parser.node.ASTStringLiteral;
import org.apache.velocity.runtime.parser.node.ASTSubtractNode;
import org.apache.velocity.runtime.parser.node.ASTText;
import org.apache.velocity.runtime.parser.node.ASTTrue;
import org.apache.velocity.runtime.parser.node.ASTWord;
import org.apache.velocity.runtime.parser.node.ASTprocess;
import org.apache.velocity.runtime.parser.node.Node;
import org.apache.velocity.runtime.parser.node.ParserVisitor;
import org.apache.velocity.runtime.parser.node.SimpleNode;
import org.vaulttec.velocity.ui.editor.VelocityEditorEnvironment;
import org.vaulttec.velocity.ui.model.Directive;
import org.vaulttec.velocity.ui.model.IBlock;
import org.vaulttec.velocity.ui.model.ITreeNode;
import org.vaulttec.velocity.ui.model.Template;

public class NodeVisitor implements ParserVisitor {

	private String fName;
	private Template fTemplate;
	private Stack<IBlock> fBlocks = new Stack<IBlock>();
	private IBlock fCurrentBlock;

	public NodeVisitor(String aName) {
		fName = aName;
	}

	public Template getTemplate() {
		return fTemplate;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.SimpleNode, java.lang.Object)
	 */
	public Object visit(SimpleNode aNode, Object aData) {
		return aNode.childrenAccept(this, aData);
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTprocess, java.lang.Object)
	 */
	public Object visit(ASTprocess aNode, Object aData) {
		fTemplate = new Template(fName);
		fCurrentBlock = fTemplate;
		return aNode.childrenAccept(this, aData);
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTComment, java.lang.Object)
	 */
	public Object visit(ASTComment aNode, Object aData) {
		return null;
	}

	
	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTStringLiteral, java.lang.Object)
	 */
	public Object visit(ASTStringLiteral aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIdentifier, java.lang.Object)
	 */
	public Object visit(ASTIdentifier aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTWord, java.lang.Object)
	 */
	public Object visit(ASTWord aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTDirective, java.lang.Object)
	 */
	public Object visit(ASTDirective aNode, Object aData) {
		String name = aNode.getDirectiveName();
		int type = Directive.getType('#' + name);
		Directive directive;
		String id = null;
		switch (type) {
			case Directive.TYPE_MACRO :
				if (aNode.jjtGetNumChildren() > 0 &&
									aNode.jjtGetChild(0) instanceof ASTWord) {
					name = aNode.jjtGetChild(0).literal();
				} else {
					name = "";
				}
				id = name;
				break;

			case Directive.TYPE_FOREACH :
				if (aNode.jjtGetNumChildren() > 2) {
					name = aNode.jjtGetChild(2).literal();
				} else {
					name = "";
				}
				if (aNode.jjtGetNumChildren() > 0 &&
							   aNode.jjtGetChild(0) instanceof ASTReference) {
					id = aNode.jjtGetChild(0).literal();
				} else {
					id = "";
				}
				break;

			case Directive.TYPE_INCLUDE :
			case Directive.TYPE_PARSE :
				if (aNode.jjtGetNumChildren() > 0) {
					name = aNode.jjtGetChild(0).literal();
				} else {
					name = "";
				}
				id = "";
				break;

			case Directive.TYPE_MACRO_CALL :

				// Check if an already defined macro is referenced
				if (VelocityEditorEnvironment.getParser().isVelocimacro(name,
														fTemplate.getName())) {
					id = "";
				} else {
					id = null;
				}
				break;

			case Directive.TYPE_USER_DIRECTIVE :
				id = "";
				break;
		}

		// If valid directive then visit embedded nodes too 
		if (id != null) {
			directive = new Directive(type, name, id,
									  (ITreeNode)fCurrentBlock,
									  aNode.getFirstToken().beginLine,
									  aNode.getLastToken().endLine);
			aData = visitBlockDirective(aNode, aData, directive, false);

			// Add parameters of macro definition
			if (type == Directive.TYPE_MACRO && aNode.jjtGetNumChildren() > 1) {
				for (int i = 1; i < aNode.jjtGetNumChildren(); i++) {
					Node node = aNode.jjtGetChild(i);
					if (node instanceof ASTReference) {
						directive.addParameter(node.literal());
					} else {
						break;
					}
				}
			}
		}
		return aData;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTBlock, java.lang.Object)
	 */
	public Object visit(ASTBlock aNode, Object aData) {
		return aNode.childrenAccept(this, aData);
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTObjectArray, java.lang.Object)
	 */
	public Object visit(ASTObjectArray aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTMethod, java.lang.Object)
	 */
	public Object visit(ASTMethod aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTReference, java.lang.Object)
	 */
	public Object visit(ASTReference aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTTrue, java.lang.Object)
	 */
	public Object visit(ASTTrue aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTFalse, java.lang.Object)
	 */
	public Object visit(ASTFalse aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTText, java.lang.Object)
	 */
	public Object visit(ASTText aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTIfStatement, java.lang.Object)
	 */
	public Object visit(ASTIfStatement aNode, Object aData) {

		// Check for first embedded #ELSE or #ELSEIF directive to get end line
		int endLine = aNode.getLastToken().endLine;
		int numChildren = aNode.jjtGetNumChildren();
		for (int i = 1; i < numChildren; i++) {
			Node node = aNode.jjtGetChild(i);
			if (node instanceof ASTElseStatement ||
										  node instanceof ASTElseIfStatement) {
				endLine = node.getFirstToken().beginLine - 1;
				break;
			}
		}
		Directive directive = new Directive(Directive.TYPE_IF,
											aNode.jjtGetChild(0).literal(),
											"", (ITreeNode)fCurrentBlock,
											aNode.getFirstToken().beginLine,
											endLine);
		return visitBlockDirective(aNode, aData, directive, false);
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTElseStatement, java.lang.Object)
	 */
	public Object visit(ASTElseStatement aNode, Object aData) {
		Directive directive = new Directive(Directive.TYPE_ELSE,
											null, "", (ITreeNode)fCurrentBlock,
											aNode.getFirstToken().beginLine,
											aNode.getLastToken().next.endLine);
		return visitBlockDirective(aNode, aData, directive, true);
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTElseIfStatement, java.lang.Object)
	 */
	public Object visit(ASTElseIfStatement aNode, Object aData) {
		Directive directive = new Directive(Directive.TYPE_ELSEIF,
											aNode.jjtGetChild(0).literal(),
											"", (ITreeNode)fCurrentBlock,
											aNode.getFirstToken().beginLine,
											aNode.getLastToken().endLine);
		return visitBlockDirective(aNode, aData, directive, true);
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTSetDirective, java.lang.Object)
	 */
	public Object visit(ASTSetDirective aNode, Object aData) {
		String expr = aNode.jjtGetChild(0).literal();
		int pos = expr.indexOf('=');
		if (pos >= 0) {
			expr = expr.substring(0, pos).trim();
		}
		Directive directive = new Directive(Directive.TYPE_SET, expr, expr,
					 (ITreeNode)fCurrentBlock, aNode.getFirstToken().beginLine,
					 aNode.getLastToken().endLine);
		fCurrentBlock.addDirective(directive);
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTExpression, java.lang.Object)
	 */
	public Object visit(ASTExpression aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTAssignment, java.lang.Object)
	 */
	public Object visit(ASTAssignment aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTOrNode, java.lang.Object)
	 */
	public Object visit(ASTOrNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTAndNode, java.lang.Object)
	 */
	public Object visit(ASTAndNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTEQNode, java.lang.Object)
	 */
	public Object visit(ASTEQNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTNENode, java.lang.Object)
	 */
	public Object visit(ASTNENode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTLTNode, java.lang.Object)
	 */
	public Object visit(ASTLTNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTGTNode, java.lang.Object)
	 */
	public Object visit(ASTGTNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTLENode, java.lang.Object)
	 */
	public Object visit(ASTLENode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTGENode, java.lang.Object)
	 */
	public Object visit(ASTGENode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTAddNode, java.lang.Object)
	 */
	public Object visit(ASTAddNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTSubtractNode, java.lang.Object)
	 */
	public Object visit(ASTSubtractNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTMulNode, java.lang.Object)
	 */
	public Object visit(ASTMulNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTDivNode, java.lang.Object)
	 */
	public Object visit(ASTDivNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTModNode, java.lang.Object)
	 */
	public Object visit(ASTModNode aNode, Object aData) {
		return null;
	}

	/**
	 * @see org.apache.velocity.runtime.parser.node.ParserVisitor#visit(org.apache.velocity.runtime.parser.node.ASTNotNode, java.lang.Object)
	 */
	public Object visit(ASTNotNode aNode, Object aData) {
		return null;
	}

	@Override
	public Object visit(ASTEscapedDirective node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object visit(ASTEscape node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object visit(ASTFloatingPointLiteral node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object visit(ASTIntegerLiteral node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object visit(ASTMap node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object visit(ASTIntegerRange node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public Object visit(ASTStop node, Object data) {
		// TODO Auto-generated method stub
		return null;
	}

	private Object visitBlockDirective(Node aNode, Object aData,
							Directive aDirective, boolean anAddToParentBlock) {
		if (anAddToParentBlock && fCurrentBlock instanceof Directive) {
			IBlock parent = (IBlock)((Directive)fCurrentBlock).getParent();
			parent.addDirective(aDirective);
		} else {
			fCurrentBlock.addDirective(aDirective);
		}
		fBlocks.push(fCurrentBlock);
		fCurrentBlock = aDirective;
		aData = aNode.childrenAccept(this, aData);
		fCurrentBlock = (IBlock)fBlocks.pop();
		return aData;
	}
}
